#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"

#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>

using namespace std;
using State = TCPTestHarness::State;

int main() {
  try {
    TCPConfig cfg{};

    // test #1: START -> LISTEN -> data without ACK or SYN (dropped) -> SYN -> SYN/ACK -> ACK
    {
      TCPTestHarness test_1(cfg);

      // tell the FSM to connect, make sure we get a SYN
      test_1.execute(Listen{});
      test_1.execute(Tick(1));

      test_1.send_byte(WrappingInt32{0}, {}, 0);
      test_1.send_fin(WrappingInt32{0}, {});
      test_1.execute(Tick(1));
      test_1.execute(ExpectState{State::LISTEN});
      test_1.execute(ExpectNoSegment{}, "test 1 failed: non-ACK data segment should be ignored");

      test_1.send_syn(WrappingInt32{0}, {});
      test_1.execute(Tick(1));

      TCPSegment seg = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(true).with_ackno(1),
                                         "test 1 failed: no SYN/ACK in response to SYN");
      test_1.execute(ExpectState{State::SYN_RCVD});

      // wrong seqno! should get ACK back but not transition
      const WrappingInt32 syn_seqno = seg.header().seqno;
      test_1.send_ack(WrappingInt32{0}, syn_seqno + 1);
      test_1.execute(Tick(1));

      test_1.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1),
                     "test 1 failed: wrong response to old seqno");

      test_1.send_ack(WrappingInt32(cfg.recv_capacity + 1), syn_seqno + 1);
      test_1.execute(Tick(1));

      test_1.execute(
          ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1));

      test_1.send_ack(WrappingInt32{1}, seg.header().seqno + 1);
      test_1.execute(Tick(1));
      test_1.execute(ExpectNoSegment{}, "test 1 failed: no need to ACK an ACK");
      test_1.execute(ExpectState{State::ESTABLISHED});
    }
  } catch (const exception &e) {
    cerr << e.what() << endl;
    return 1;
  }

  return EXIT_SUCCESS;
}
